# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2019/11/3 19:57
# @Author  : wenjxu
# @Email   : wenjxususu@163.com
# @File    : core.py
# @Software: PyCharm
import json, os, sys, base64, time, re, logging
from json import JSONDecodeError
from urllib.parse import urlparse

BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
sys.path.insert(0, BASE_DIR)

from core.harTocase.harTocase import utils

# 忽略的请求头
IGNORE_REQUEST_HEADERS = [
    "host",
    "accept",
    "content-length",
    "connection",
    "accept-encoding",
    "accept-language",
    "origin",
    "referer",
    "cache-control",
    "pragma",
    "cookie",
    "upgrade-insecure-requests",
    ":authority",
    ":method",
    ":scheme",
    ":path"
]
# 忽略的请求参数
IGNORE_PARAMS = [
    # "preset_parameters",
    # "public_parameters",
]
# 常见的字段
COMMON_FIELD = [
    "code",
    "status",
    "status_code",
    "message",
    "msg"
]


class HarObj(object):
    def __make_request_url(self, single_interface):

        '''
            return_dict:{'url': 'http://127.0.0.1:5000/api/get-token', 'params': {}}
        '''

        returns_dict = {}

        returns_dict["url"] = urlparse(single_interface["request"]["url"]).geturl()

        request_params = utils.convert_list_to_dict(
            single_interface["request"].get("queryString", [])
        )

        for ignore_param in IGNORE_PARAMS:  # IGNORE_PARAMS 忽略一些参数
            if ignore_param in returns_dict.get("params", {}):
                returns_dict.get("params").pop(ignore_param)

        # for k, v in request_params.items():  # 针对参数是$的进行处理
        #     if "$" in v:
        #         v = re.sub('\$', '$$', v)
        #     request_params[k] = v

        if request_params:
            returns_dict["params"] = request_params
        else:
            returns_dict["params"] = {}

        return returns_dict

    def __make_request_method(self, single_interface):
        '''
            return_dict:{'method': 'POST'}
        '''

        returns_dict = {}
        method = single_interface["request"]["method"]
        if not method:
            logging.exception("method missed in request.")
            return

        returns_dict["method"] = method

        return returns_dict

    def __make_request_data(self, single_interface):
        '''
           return_dict: {"data": {"v": "1", "w": "2"}} or {"json": {"v": "1", "w": "2"}}
        '''

        returns_dict = {}
        method = single_interface["request"].get("method")
        if method in ["POST", "PUT", "PATCH"]:
            postdata = single_interface["request"].get("postData", {})
            mimeType = postdata.get("mimeType")

            if "text" in postdata:
                post_data = postdata.get("text")
            else:
                params = postdata.get("params", [])
                post_data = utils.convert_list_to_dict(params)

            request_data_key = "data"
            if not mimeType:
                pass

            elif mimeType.startswith("application/json"):
                try:
                    post_data = json.loads(post_data)
                    request_data_key = "json"
                except JSONDecodeError:
                    pass

            elif mimeType.startswith("application/x-www-form-urlencoded"):
                post_data = utils.convert_x_www_form_urlencoded_to_dict(post_data)

            elif mimeType.startswith("multipart/form-data"):
                post_data = utils.convert_form_data_to_dict(post_data)

            else:
                pass

            for ignore_param in IGNORE_PARAMS:  # IGNORE_PARAMS 忽略一些参数
                if ignore_param in post_data:
                    post_data.pop(ignore_param)

            for k, v in post_data.items():  # 针对参数是$的进行处理
                if isinstance(v,int):
                    continue
                if not v:
                    continue
                if "$" in v:
                    v = re.sub('\$', '$$', v)
                post_data[k] = v

            returns_dict[request_data_key] = post_data
        return returns_dict

    def __make_headers(self, single_interface):
        '''
        return_dict:
        {
            headers: {"Content-Type": "application/json"}
        }
        '''
        returns_dict = {}
        header_dict = {}

        headers_list = single_interface["request"].get("headers", [])
        if not headers_list:
            returns_dict["headers"] = {}
            return returns_dict

        for header in headers_list:  # 忽略一些字段
            if header["name"].lower() in IGNORE_REQUEST_HEADERS:
                continue
            header_dict[header["name"]] = header["value"]

        for k in list(header_dict.keys()):  # 忽略form-data请求头
            if "form-data" in header_dict[k]:
                header_dict.pop(k)

        returns_dict["headers"] = header_dict

        return returns_dict

    def __make_validate(self, single_interface):
        '''

        single_interface (dict):
            {
                "request": {},
                "response": {
                    "status": 200,
                    "headers": [
                        {
                            "name": "Content-Type",
                            "value": "application/json; charset=utf-8"
                        },
                    ],
                    "content": {
                        "size": 71,
                        "mimeType": "application/json; charset=utf-8",
                        "text": "eyJJc1N1Y2Nlc3MiOnRydWUsIkNvZGUiOjIwMCwiTWVzc2FnZSI6bnVsbCwiVmFsdWUiOnsiQmxuUmVzdWx0Ijp0cnVlfX0=",
                        "encoding": "base64"
                    }
                }
            }

        returns_dict: {
                "validate":[
                    {"len_ge":["content.jsonObj",0]}
                ]
        }
        '''
        returns_dict = {}
        validate_list = []


        resp = single_interface.get("response")
        validate_list.append(
            {"eq": ["status_code", resp.get("status")]}
        )

        content = resp.get("content")
        text = content.get("text")

        if not text:
            return

        mime_type = content.get("mimeType")

        if mime_type and mime_type.startswith("application/json"):

            encoding = content.get("encoding")
            if encoding and encoding == "base64":
                base64_text = base64.b64decode(text).decode('utf-8')
            else:
                base64_text = text
            try:
                text = json.loads(base64_text)

            except JSONDecodeError:
                logging.warning(
                    "response content can not be loaded as json: {}".format(content.encode("utf-8"))
                )
                return

        elif mime_type and mime_type.startswith("text/html;charset=utf-8"):
            try:
                text = json.loads(text)
            except JSONDecodeError:
                logging.warning(
                    "response content can not be loaded as json: {}".format(text.encode("utf-8"))
                )
                return

        if not isinstance(text, dict):
            return

        for key, value in text.items():
            if isinstance(value, (dict, list)):
                continue

            if key in COMMON_FIELD:
                validate_list.append({
                    "eq": ["content.{}".format(key), value]
                })
        returns_dict["validate"] = validate_list

        return returns_dict

    def gen_single_interface(self, single_interface):
        url = self.__make_request_url(single_interface).get("url")
        params = self.__make_request_url(single_interface).get("params")
        method = self.__make_request_method(single_interface).get("method")
        data = self.__make_request_data(single_interface)
        headers = self.__make_headers(single_interface).get("headers")
        validate = self.__make_validate(single_interface).get("validate")
        returns_dict = {
            "url": url,
            "params": params,
            "method": method,
            "headers": headers,
            "validate": validate
        }

        if "files" in data.get("data", {}):
            returns_dict.update({"files": data.get("data").pop("files")})

        returns_dict.update(data)

        return returns_dict


    def gen_interface_element_list(self,interface_list):
        '''
        returns_list：
            [
                {
                    "url":"http://127.0.0.1:5000/api/get-token"
                    "method":"POST",
                    "data" or "json" :{
                        "v": "3.9.0",
                        "from": "3"
                    },
                    "param":{
                        "user_id":"111111",
                        "version_code":390
                    },
                    "headers":{
                        "Content-Type": "application/json"
                    },
                    "validate":[
                        {"len_ge":["content.jsonObj",0]},
                        {"eq":["content.code",0]}
                    ]
                },
                {...},
            ]
        '''
        returns_list = []

        for single_interface in interface_list:
            returns_list.append(self.gen_single_interface(single_interface))

        return returns_list

class HrunObj:

    def __prepare_config(self, single_interface_element):
        '''
        "name":"test api describe",
        "base_url":"http://test.baletoo.com",
        "variables":[
            {"user_id":111111},
            {"version_code":390},
        ]
        '''
        returns_dict = {
            "name": "testapi describe",
            "base_url": "",
            "variables": [],
        }
        variables = returns_dict["variables"]

        returns_dict["base_url"] = utils.convert_url_parse_to_domain(single_interface_element.get("url"))

        if single_interface_element.get("data"):
            for data_dict in utils.convert_dict_to_list(single_interface_element["data"]):  # 组织data添加到variables
                variables.append(data_dict)

        if single_interface_element.get("json"):
            for json_dict in utils.convert_dict_to_list(single_interface_element["json"]):  # 组织json添加到variables
                variables.append(json_dict)

        if single_interface_element.get("params"):
            for params_dict in utils.convert_dict_to_list(single_interface_element["params"]):  # 组织params添加到variables
                variables.append(params_dict)

        return returns_dict

    def __prepare_request(self, single_interface_element):
        '''
        returns_dict:{
            request:{
                "url":"/Operator/Operator/uploadWorkTrack",
                "method":"POST",
                "data":{
                    "user_id":111111
                },
                "param":{
                    "version_code":390
                },
                "headers":{
                    "Content-Type": "application/json"
                }
            }
        }
        '''

        returns_dict = {
            "request": {},
        }
        request = returns_dict["request"]

        request["url"] = utils.convert_url_parse_to_path(single_interface_element.get("url", None))  # 组织request中的url
        request["method"] = single_interface_element.get("method", None)  # 组织request中的method

        if single_interface_element.get("data"):
            request["data"] = utils.convert_value_to_variable(single_interface_element["data"])  # 组织request中的data

        if single_interface_element.get("json"):
            request["json"] = utils.convert_value_to_variable(single_interface_element["json"])  # 组织request中的json

        if single_interface_element.get("params"):
            request["params"] = utils.convert_value_to_variable(single_interface_element["params"])  # 组织request中的params

        if single_interface_element.get("files"):
            request["files"] = single_interface_element["files"]  # 组织request中的files

        if single_interface_element.get("headers"):
            request["headers"] = single_interface_element["headers"]  # 组织request中的headers

        return returns_dict

    def __prepare_validate(self, single_interface_element):
        '''
        returns_dict:{
            validate: [
                {'eq': ['content.statusText', 'OK']},
                {'eq': ['content.status', 200]}
            ]
        }
        '''

        returns_dict = {
            "validate": [],
        }
        validate = returns_dict["validate"]

        if single_interface_element.get("validate"):
            for validate_dict in single_interface_element.get("validate"):
                validate.append(validate_dict)

        return returns_dict

    def __prepare_extract(self, single_interface_element):
        '''
        returns_dict：{
            "extract": [
                {"code","content.code"},
                {"code","content.msg"},
            ]
        }
        '''
        returns_dict = {
            "extract": []
        }

        return returns_dict

    def __prepare_hooks(self):
        '''
        returns_dict：{
            "setup_hooks":[
                "${setup_hook_prepare_kwargs($request)}",
                "${setup_hook_httpntlmauth($request)}"
            ],
            "teardown_hooks":[
                "${teardown_hook_sleep_N_secs($response, 2)}"
            ]
        }
        '''
        returns_dict = {
            "setup_hooks": [],
            "teardown_hooks": []
        }
        return returns_dict

    def gen_HrunAPI(self, single_interface_element):
        config = self.__prepare_config(single_interface_element)
        request = self.__prepare_request(single_interface_element)
        validate = self.__prepare_validate(single_interface_element)
        testapi_dict = {
            "name": config["name"],
            "base_url": config["base_url"],
            "variables": config["variables"],
            "request": request["request"],
            "validate": validate["validate"]
        }

        return testapi_dict

    def gen_HrunTestCase(self, single_interface, api_filepath=''):
        return {
            "test": {
                "name": "teststep describe",
                "api": "api/" + api_filepath,
                "variables": [
                    {"__example": "someting"}
                ],
                "extract": self.__prepare_extract(single_interface).get("extract"),
                "validate": self.__prepare_validate(single_interface).get("validate"),
                "setup_hooks": self.__prepare_hooks().get("setup_hooks"),
                "teardown_hooks": self.__prepare_hooks().get("teardown_hooks")
            }
        }

class HarParser(HarObj, HrunObj):
    def __init__(self, harfile_path):

        self.harfile_path = harfile_path
        self.interface_list = utils.loads_json(self.harfile_path)["log"]["entries"]

    def gen_testapi(self):
        '''
        returns_dict:{
            "name":"testapi describe",
            "base_url":"http://test.baletoo.com",
            "variables":[
                {"user_id":111111},
                {"version_code":390},
            ],
            request:{
                "url":"/Operator/Operator/uploadWorkTrack",
                "method":"POST",
                "data" or "json" :{
                    "user_id":$user_id
                },
                "param":{
                    "version_code":$version_code
                },
                "headers":{
                    "Content-Type": "application/json"
                }
            },
            validate: [
                {'eq': ['content.statusText', 'OK']},
                {'eq': ['content.status', 200]}
            ]
        }
        '''

        testapi_list = []
        interface_element_list = self.gen_interface_element_list(self.interface_list)
        for single_interface_element in interface_element_list:
            testapi_list.append(self.gen_HrunAPI(single_interface_element))

        return testapi_list

    def gen_testapi_yaml(self):
        api_dirpath = os.path.join(BASE_DIR, "api")
        testapi_list = self.gen_testapi()
        for single_testapi in testapi_list:
            api_filename = "_".join(single_testapi.get("request").get("url").lstrip("/").split("/"))

            if api_filename + ".yaml" in os.listdir(api_dirpath):
                utils.dump_yamlfile(
                    single_testapi, api_dirpath + "/{}{}.yaml".format(api_filename, time.time()))
            else:
                utils.dump_yamlfile(
                    single_testapi, api_dirpath + "/{}.yaml".format(api_filename))
            logging.info("Generate YAML testapi successfully:{}".format(api_filename))


    def gen_testcase(self):
        '''
        returns_list: [
            {
                "config": {
                    "name": "create user and check result. ",
                    "variables": [
                    ],
                    "output":[
                    ],
                }
            },
            {
                "test": {
                    "name": "ai对弈历史",
                    "api" : "api/get_token.yaml",
                    "variables": [
                    ],
                    "extract": [
                        {"code","content.code"},
                        {"code","content.msg"},
                    ],
                    "validate": [
                         {"len_ge":["content.jsonObj",0]},
                         {"eq":["content.code",0]},
                    ],
                    "setup_hooks":[],
                    "teardown_hooks":[]
                }
            },
            {...},
            {...},
        ]
        '''
        interface_element_list = self.gen_interface_element_list(self.interface_list)
        returns_list = [
            {
                "config": {
                    "name": "testcase describe ",
                    "variables": [
                    ],
                    "output": [
                    ],
                }
            },
        ]
        for single_interface in interface_element_list:
            api_filepath = "_".join(
                utils.convert_url_parse_to_path(single_interface["url"]).lstrip("/").split("/")) + ".yaml"

            returns_list.append(self.gen_HrunTestCase(single_interface, api_filepath))

        return returns_list

    def gen_testcase_json(self):
        testcases_dirpath = os.path.join(BASE_DIR, "testcases")
        testcase_data = self.gen_testcase()
        testcase_filename = os.path.basename(self.harfile_path).strip(".har")

        if testcase_filename + ".json" in os.listdir(testcases_dirpath):
            utils.dump_jsonfile(
                testcase_data, testcases_dirpath + "/{}{}.json".format(testcase_filename, time.time()))
        else:
            utils.dump_jsonfile(
                testcase_data, testcases_dirpath + "/{}.json".format(testcase_filename))

        logging.info("Generate JSON testcase successfully:{}".format(self.harfile_path))


if __name__ == '__main__':
    HarParser(utils.demo).gen_testapi_yaml()
    pass
